package com.briup.sms.storage;

import com.briup.day13.AssertUtil;
import com.briup.sms.model.Student;
import com.briup.sms.model.User;
import lombok.Getter;

import java.util.Arrays;
import java.util.StringJoiner;

/**
 * 用于数据存储的存储类，负责数据的存储和管理，
 * 要求所有的数据都能够通过本类进行存储和管理；
 * <p>
 * 使用泛型，能够在编译期间对你传入的对象类型进行语法检查，
 * 如果不是你设定的泛型类型，那么将会编译报错；
 * <p>
 * 泛型就是指在定义某一个方法或类时，不指定该方法参数/类参数的类型，
 * 而是使用一个标识代指，然后在使用该类/方法时，根据你实际传入的参数类型
 * 决定其应该使用哪个类型，强制限定
 */
public class Storage<T> {
    /**
     * 用于实际存储和管理数据的数组
     */
    private Object[] values;
    /**
     * 用于统计数组中实际元素的个数
     */
    @Getter
    private int size = 0;

    public Storage() {
        this(10);
    }

    public Storage(int capacity) {
        values = new Object[capacity];
    }

    // 对数据的存储和管理，无非就是增删改查
    public void add(T obj) {
        // 在添加元素之前，对数组的长度范围进行校验
        checkCapacity(size + 1);
        values[size++] = obj;
    }

    /**
     * 批量添加用户数据
     *
     * @param objs 数据数组对象
     */
    public void add(T[] objs) {
        int len = objs.length;
        checkCapacity(size + len);
        // 直接将你传入的用户数组数据写入到数组原有数据后面即可
        // 从values数组中的size下标开始写起，写入Students.length个元素
        System.arraycopy(objs, 0, values, size, len);
        // 修改size，调整数组中元素的实际个数
        size += len;
    }

    /**
     * 根据指定的下标进行删除
     *
     * @param index 下标
     * @return 被删除的元素对象
     */
    public T delete(int index) {
        // 先校验该下标是否可用
        checkRange(index);
        // 根据下标找到该下标位置的元素
        Object obj = values[index];
        // 删除元素，即将该下标位置后的元素覆盖掉该元素即可
        System.arraycopy(values, index + 1, values, index,
                size - index - 1);
        // 最后一个元素位置需要置空
        values[--size] = null;
        return (T) obj;
    }

    /**
     * 将传入的对象设置到指定下标位置中
     *
     * @param index 下标
     * @param obj   更新后的对象
     */
    public T set(int index, T obj) {
        checkRange(index);
        Object value = values[index];
        values[index] = obj;
        return (T) value;
    }

    /**
     * 获取指定下标位置的元素
     *
     * @param index 下标
     * @return 获取的元素对象
     */
    public T get(int index) {
        checkRange(index);
        return (T) values[index];
    }

    private void checkRange(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException(
                    String.format("下标越界：长度为%s，您传入的为%s", size, index)
            );
        }
    }

    /**
     * 根据传入的参数与当前数组长度进行比较，
     * 查看添加当前元素是否会越界
     *
     * @param i 元素个数
     */
    private void checkCapacity(int i) {
        // 如果当前i小于等于数组长度，那么直接继续添加元素即可
        if (i <= values.length) {
            return;
        } else {
            // 如果i大于数组长度，那么说明当前数组无法支持新数据的存储，
            // 所以应该对当前数组进行扩容
            grow(i);
        }
    }

    /**
     * 对数组进行扩容的方法
     *
     * @param size 添加元素至少所需的数组大小
     */
    private void grow(int size) {
        // 获取原始的数组容量
        int oldCapacity = values.length;
        // 计算新数组的容量，建议新数组的容量是原本数组容量的2倍
        // 如果添加元素所需的数组大小至少是2倍以上，
        // 那么就使用size作为数组的容量
        int newCapacity = Math.max(oldCapacity << 1, size);
        // 创建新数组对象，并且将原有数组的数据写如到新数组中
        values = Arrays.copyOf(values, newCapacity);
    }

    public String toString() {
        StringJoiner sj = new StringJoiner(",", "管理的数据如下：[", "]");
        for (int i = 0; i < size; i++) {
            sj.add(values[i].toString());
        }
        return sj.toString();
    }
}
